search_scope.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import itertools
  2. import logging
  3. import os
  4. import posixpath
  5. from pip._vendor.packaging.utils import canonicalize_name
  6. from pip._vendor.six.moves.urllib import parse as urllib_parse
  7. from pip._internal.models.index import PyPI
  8. from pip._internal.utils.compat import has_tls
  9. from pip._internal.utils.misc import normalize_path, redact_auth_from_url
  10. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  11. if MYPY_CHECK_RUNNING:
  12. from typing import List
  13. logger = logging.getLogger(__name__)
  14. class SearchScope(object):
  15. """
  16. Encapsulates the locations that pip is configured to search.
  17. """
  18. __slots__ = ["find_links", "index_urls"]
  19. @classmethod
  20. def create(
  21. cls,
  22. find_links, # type: List[str]
  23. index_urls, # type: List[str]
  24. ):
  25. # type: (...) -> SearchScope
  26. """
  27. Create a SearchScope object after normalizing the `find_links`.
  28. """
  29. # Build find_links. If an argument starts with ~, it may be
  30. # a local file relative to a home directory. So try normalizing
  31. # it and if it exists, use the normalized version.
  32. # This is deliberately conservative - it might be fine just to
  33. # blindly normalize anything starting with a ~...
  34. built_find_links = [] # type: List[str]
  35. for link in find_links:
  36. if link.startswith('~'):
  37. new_link = normalize_path(link)
  38. if os.path.exists(new_link):
  39. link = new_link
  40. built_find_links.append(link)
  41. # If we don't have TLS enabled, then WARN if anyplace we're looking
  42. # relies on TLS.
  43. if not has_tls():
  44. for link in itertools.chain(index_urls, built_find_links):
  45. parsed = urllib_parse.urlparse(link)
  46. if parsed.scheme == 'https':
  47. logger.warning(
  48. 'pip is configured with locations that require '
  49. 'TLS/SSL, however the ssl module in Python is not '
  50. 'available.'
  51. )
  52. break
  53. return cls(
  54. find_links=built_find_links,
  55. index_urls=index_urls,
  56. )
  57. def __init__(
  58. self,
  59. find_links, # type: List[str]
  60. index_urls, # type: List[str]
  61. ):
  62. # type: (...) -> None
  63. self.find_links = find_links
  64. self.index_urls = index_urls
  65. def get_formatted_locations(self):
  66. # type: () -> str
  67. lines = []
  68. redacted_index_urls = []
  69. if self.index_urls and self.index_urls != [PyPI.simple_url]:
  70. for url in self.index_urls:
  71. redacted_index_url = redact_auth_from_url(url)
  72. # Parse the URL
  73. purl = urllib_parse.urlsplit(redacted_index_url)
  74. # URL is generally invalid if scheme and netloc is missing
  75. # there are issues with Python and URL parsing, so this test
  76. # is a bit crude. See bpo-20271, bpo-23505. Python doesn't
  77. # always parse invalid URLs correctly - it should raise
  78. # exceptions for malformed URLs
  79. if not purl.scheme and not purl.netloc:
  80. logger.warning(
  81. 'The index url "%s" seems invalid, '
  82. 'please provide a scheme.', redacted_index_url)
  83. redacted_index_urls.append(redacted_index_url)
  84. lines.append('Looking in indexes: {}'.format(
  85. ', '.join(redacted_index_urls)))
  86. if self.find_links:
  87. lines.append(
  88. 'Looking in links: {}'.format(', '.join(
  89. redact_auth_from_url(url) for url in self.find_links))
  90. )
  91. return '\n'.join(lines)
  92. def get_index_urls_locations(self, project_name):
  93. # type: (str) -> List[str]
  94. """Returns the locations found via self.index_urls
  95. Checks the url_name on the main (first in the list) index and
  96. use this url_name to produce all locations
  97. """
  98. def mkurl_pypi_url(url):
  99. # type: (str) -> str
  100. loc = posixpath.join(
  101. url,
  102. urllib_parse.quote(canonicalize_name(project_name)))
  103. # For maximum compatibility with easy_install, ensure the path
  104. # ends in a trailing slash. Although this isn't in the spec
  105. # (and PyPI can handle it without the slash) some other index
  106. # implementations might break if they relied on easy_install's
  107. # behavior.
  108. if not loc.endswith('/'):
  109. loc = loc + '/'
  110. return loc
  111. return [mkurl_pypi_url(url) for url in self.index_urls]